#include "stdafx.h"
#include "raymarching.h"

#include "Scene.h"
#include "Math.h"

#include "Perlin.h"

const float EPS = 0.0001f;

const float WATER_HEIGHT = -1.2f;
const float WATER_LEVEL_SMOOTH_OFFSET = 0.1f;
//229 145
int debugX, debugY;


float heightFunc(float x, float z)
{
	//float f = 0.0f;
	//float w = 0.5f;
	//for( int i=0; i < 4 ; i++ )
	//{
	//	float n = noise( x, 0, z );
	//	f += w * n;
	//	w *= 0.5f;
	//	x *= 2.0f;
	//	z *= 2.0f;
	//}
	//return f;

	//float f = noiseLoop(x, 0, z, 0.1f, 2, 0.2f);
	//float f = fractalSum(x, 0, z, 0.1f, 8) / 4;
	//if(f < WATER_HEIGHT + WATER_LEVEL_SMOOTH_OFFSET)
	//{
	//	f = WATER_HEIGHT + WATER_LEVEL_SMOOTH_OFFSET * smoothstep(WATER_HEIGHT, WATER_HEIGHT + WATER_LEVEL_SMOOTH_OFFSET, f);
	//}
	float f = max(fractalSum(x, 0, z, 0.1f, 8) / 4, WATER_HEIGHT);

	return f;
	//return noise(x, 0, z);
	//return sinf(x) * sinf(z) + 1 * sinf(x / 4);
}

float getSampleDepth(const Ray & ray, float & outHeight, float maxt = 100.0f)
{
	const float dtConst = 0.1f;
	float delt = dtConst;
	const float mint = 0.1f;

	float lh = 0;
	float ly = 0;


	float tFinal = 0;
	//float heightFinal = 0;

	for(float t = mint; t < maxt; t += delt)
	{
		XMVECTOR p = ray.Position + ray.Direction * t;
		float px = XMVectorGetX(p);
		float py = XMVectorGetY(p);
		float pz = XMVectorGetZ(p);

		float height = heightFunc(px, pz);
		if(height > py)
		{
			//tFinal = t - 0.5f * delt;
			tFinal = t - delt + delt*(lh-ly)/(py-ly-height+lh);

			outHeight = height;
			//heightFinal = height;
			break;
		}

		delt = max(dtConst * t, dtConst);

		lh = height;
		ly = py;
	}

	return tFinal;
}

XMVECTOR getHeightFuncNormal(XMVECTOR position)
{
	float x = position.m128_f32[0];
	float y = position.m128_f32[1];
	float z = position.m128_f32[2];
	XMVECTOR normal = XMVectorSet(
		heightFunc(x - EPS, z) - heightFunc(x + EPS, z),
		2 * EPS,
		heightFunc(x, z - EPS) - heightFunc(x, z + EPS),
		0);

	return XMVector3Normalize(normal);
}

XMVECTOR getTerrainShading(XMVECTOR position, XMVECTOR normal, const Scene & scene)
{
	XMVECTOR diffAccum = scene.AmbientColor;
	XMVECTOR specAccum = XMVectorZero();

	size_t pLightsSize = scene.PointLights.size();
	for (size_t i = 0; i < pLightsSize ; i++)
	{
		const PointLight & light = scene.PointLights[i];
		XMVECTOR toLight = light.Position - position;
		float toLightLen = XMVectorGetX(XMVector3Length(toLight));
		toLight = XMVector3Normalize(toLight);

		Ray toLightRay(position, toLight);

		float tmp;
		float depth = getSampleDepth(toLightRay, tmp, toLightLen);

		if(depth > 0.0f) continue;

		float diffTerm = XMVectorGetX(XMVectorSaturate(XMVector3Dot(normal, toLight)));

		//XMVECTOR refl = XMVectorSubtract(toLight, XMVectorMultiply(XMVectorReplicate(2.0f), XMVectorMultiply(XMVector3Dot(toLight, normal), normal)));
		//float specBase = XMVectorGetX(XMVectorSaturate(XMVectorNegate(XMVector3Dot(refl, XMVectorNegate(ray.Dir)))));
		//// if(base > 0.9f)

		//float specTerm = 0.0f;

		//if(specBase > 0.9f)
		//{
		//	specTerm = powf(specBase, mat->SpecularPower);
		//}

		//const float strength = light->Strength * powf( 1.0f - saturate( XMVectorGetX(toLightLen) / light->Radius ), light->Falloff );
		float strength = light.Strength * ( 1.0f - saturate( toLightLen / light.Radius ) );

		diffAccum += diffTerm * strength * light.Color;
		//specAccum = XMVectorAdd( specAccum, XMVectorMultiply(light->Diffuse, XMVectorReplicate(specTerm * strength)) );
	}

	return diffAccum;
}

XMVECTOR getTerrainMaterial(XMVECTOR position, XMVECTOR normal)
{
	const float SNOW_HEIGHT = -0.4f;
	const float SNOW_ZERO_LEVEL_MAX_SLOPE = 0.3f;
	const float SNOW_MAX_SLOPE = 0.7f;

	float x = position.m128_f32[0];
	float y = position.m128_f32[1];
	float z = position.m128_f32[2];

	float localSnowHeight = SNOW_HEIGHT + noise(10 * x, 10 * y, 10 * z) * 0.5f;

	float terrainSlope = XMVectorGetX(XMVector3Dot(normal, XMVectorSet(1, 0, 0, 0)));

	//float localSnowMaxSlope = SNOW_MAX_SLOPE + noise(10 * x, 10 * y, 10 * z) * 0.1f;
	float localSnowMaxSlope = (std::min)(SNOW_ZERO_LEVEL_MAX_SLOPE + y, SNOW_MAX_SLOPE) + noise(2*x, 2*y, 2*z) * 1.0f;

	float snowStrength = 0.75f * (std::min)((position.m128_f32[1] - SNOW_HEIGHT), 0.4f) +
						 1.0f * (1.0f - fabsf(terrainSlope));

	//snowStrength += noise(x, y, z) * 1.0f;

	const float SNOW_STRENGTH_BORDER = 0.8f;
	const float SNOW_SMOOTH_OFFSET = 0.1f;

	XMVECTOR groundColor = XMVectorSet(0.7f, 0.4f, 0.3f, 0.0f);
	XMVECTOR snowColor = XMVectorSet(0.9f, 0.9f, 0.9f, 0.0f);

	return smoothInterp(groundColor,  snowColor,
			(snowStrength - SNOW_STRENGTH_BORDER) / SNOW_SMOOTH_OFFSET);

	if(position.m128_f32[1] > localSnowHeight && fabsf(terrainSlope) < localSnowMaxSlope)
		return XMVectorSet(0.9f, 0.9f, 0.9f, 0.0f);

	return XMVectorSet(0.7f, 0.4f, 0.3f, 0.0f);
}

XMVECTOR getTerrainColor(const Ray & ray, float sampleDepth, float heightValue, const Scene & scene)
{
	XMVECTOR samplePos = ray.Position + ray.Direction * sampleDepth;

	XMVECTOR normal = getHeightFuncNormal(samplePos);

	//bool lol = false;
	//if(debugX == 229 && debugY == 145) lol = true;
	//if(lol) printf("\nLOL height: %f\n", heightValue);

	if(heightValue == WATER_HEIGHT)
	{
		XMVECTOR shading = getTerrainShading(samplePos, normal, scene);
		XMVECTOR material = XMVectorSet(0.0f, 0.6f, 0.9f, 0.0f);
		return XMVectorSetW(shading * material, 1);
	}
	//else if(heightValue < WATER_HEIGHT + WATER_LEVEL_SMOOTH_OFFSET)
	//{
	//	XMVECTOR shading = getTerrainShading(samplePos, normal, scene);
	//	XMVECTOR waterMaterial = XMVectorSet(0.0f, 0.6f, 0.9f, 0.0f);
	//	XMVECTOR terrainMaterial = getTerrainMaterial(samplePos, normal);

	//	XMVECTOR combinedMaterial = smoothInterp(waterMaterial, terrainMaterial,
	//		(heightValue - WATER_HEIGHT) / WATER_LEVEL_SMOOTH_OFFSET);

	//	return XMVectorSetW(shading * combinedMaterial, 1);
	//}
	else
	{
		XMVECTOR shading = getTerrainShading(samplePos, normal, scene);
		XMVECTOR material = getTerrainMaterial(samplePos, normal);
		return XMVectorSetW(shading * material, 1);
	}

	
	//return XMVectorSet(samplePos.m128_f32[1] * 0.5f + 0.5f, 0, 0, 1);
}

XMVECTOR raymarch(const Ray & ray, const Scene & scene, int dX, int dY)
{
	debugX = dX; debugY = dY;
	float height;
	float sampleDepth = getSampleDepth(ray, height);

	if(sampleDepth == 0.0f) // sky
	{
		return XMVectorSet(0.6f, 0.75f, 0.9f, 1);
	}

	return getTerrainColor(ray, sampleDepth, height, scene);

	

	//return XMVectorSet(rand() / (float)RAND_MAX, 0, 0, 1);
}
